#include "stdafx.h"
#include "U7Objs.h"
#include "MapChunk.h"
#include "WorldMap.h"
#include "U7FilesLoader.h"
#include "Shape.h"

#include "U7Objs.inl"

#include <map>

struct PatchRecord
{
	U7VECTOR2	chunkCoords;
	U7VECTOR3	tileCoords;
	SHAPEID		shapeID;

	PatchRecord()
	{
	}
	PatchRecord( int xchunk, int ychunk, int xt, int yt, int zlevel, int shape, int frame )
	{
		chunkCoords.x = xchunk;
		chunkCoords.y = ychunk;
		tileCoords.x = xt;
		tileCoords.y = yt;
		tileCoords.z = zlevel;
		shapeID.shapeType = shape;
		shapeID.shapeFrame = frame;
	}

	bool operator < ( const PatchRecord& x ) const
	{
		if ( chunkCoords.x < x.chunkCoords.x )
			return true;
		if ( chunkCoords.x == x.chunkCoords.x )
		{
			if ( chunkCoords.y < x.chunkCoords.y )
				return true;
			if ( chunkCoords.y == x.chunkCoords.y )
			{
				if ( tileCoords.x < x.tileCoords.x )
					return true;
				if ( tileCoords.x == x.tileCoords.x )
				{
					if (tileCoords.y < x.tileCoords.y )
						return true;
					if ( tileCoords.y == x.tileCoords.y )
						return (tileCoords.z < x.tileCoords.z );
				}
			}
		}
		return false;
	}
};

static std::map<PatchRecord,PatchRecord> gObjPatchMap;

static void InitPatchMap()
{
	if ( gObjPatchMap.size() == 0 )
	{
		gObjPatchMap[PatchRecord(56,71,11,1,1,879,17)]=PatchRecord(56,71,11,1,0,879,17);
		gObjPatchMap[PatchRecord(56,71,11,6,1,879,17)]=PatchRecord(56,71,11,6,0,879,17);

		gObjPatchMap[PatchRecord(57,71,6,1,1,879,17)]=PatchRecord(57,71,6,1,0,879,17);
		gObjPatchMap[PatchRecord(57,71,6,6,1,879,17)]=PatchRecord(57,71,6,6,0,879,17);
		gObjPatchMap[PatchRecord(57,71,1,6,1,879,17)]=PatchRecord(57,71,1,6,0,879,17);
		gObjPatchMap[PatchRecord(57,71,0,6,1,879,17)]=PatchRecord(57,71,0,6,0,879,17);
	}
}

void WorldMap::Patch( BasicObject* obj )
{
	PatchRecord thisRecord;
	obj->GetMapChunk()->GetChunkCoords(&thisRecord.chunkCoords);
	obj->GetLocalCoords(&thisRecord.tileCoords);

	auto a = gObjPatchMap.find(thisRecord);
	if ( a != gObjPatchMap.end() )
	{
		obj->SetLocalCoords(a->second.tileCoords.x,a->second.tileCoords.y,a->second.tileCoords.z);
	}
}

WorldMap::WorldMap( U7FilesLoader* files, TimeQueue* tq )
{
	u7Files = files;
	tqueue = tq;
	memset( mapChunks, NULL, sizeof(mapChunks) );
	memset( loadedSuperchunks, false, sizeof(loadedSuperchunks) );
}

WorldMap::~WorldMap()
{
	for ( int x = 0; x < CHUNKS_PER_MAP_IN_WIDTH; x++ )
	{
		for ( int y = 0; y < CHUNKS_PER_MAP_IN_HEIGHT; y++ )
		{
			if ( mapChunks[x][y] )
				delete mapChunks[x][y];
		}
	}
}

MapChunk* WorldMap::CreateMapChunk( int chx, int chy )
{
	assert(	mapChunks[chx][chy] == NULL );
	return (mapChunks[chx][chy] = new MapChunk(this,chx,chy));
}

void WorldMap::LoadSuperchunk( int iregFileIndex )
{
	int xschIndex = (iregFileIndex%SUPERCHUNKS_PER_MAP_IN_WIDTH);
	int yschIndex = (iregFileIndex/SUPERCHUNKS_PER_MAP_IN_WIDTH);

	int xchStart = xschIndex * CHUNKS_PER_SUPERCHUNK_IN_WIDTH;
	int ychStart = yschIndex * CHUNKS_PER_SUPERCHUNK_IN_HEIGHT;

	TRACE( "WorldMap::LoadSuperchunk(%d), superchunk(%d,%d)\n",
		iregFileIndex, xschIndex, yschIndex );

	// prepare all mapchunks for this superchunk
	for ( int y = 0; y < CHUNKS_PER_SUPERCHUNK_IN_WIDTH; y++ )
	{
		for ( int x = 0; x < CHUNKS_PER_SUPERCHUNK_IN_WIDTH; x++ )
		{
			MapChunk* mchk = GetMapChunk( xchStart + x, ychStart + y );

			// set pattern ID for this
			if (1) // control to load or not
			{
				PATTERN_ID pid = ((MAP)u7Files->mapFile)[yschIndex][xschIndex][y][x];
				mchk->SetTerrain(pid);
			}

			// set ifix objects
			if (1) // control to load or not
			{
				PFLEXFILEHEADER ifixHeader = (PFLEXFILEHEADER)u7Files->ifixFiles[yschIndex*SUPERCHUNKS_PER_MAP_IN_WIDTH+xschIndex];
				if ( ifixHeader != NULL )
				{
					PFLEXFILEENTRY entry = ((PFLEXFILEENTRY)(ifixHeader+1)) + (y*CHUNKS_PER_SUPERCHUNK_IN_WIDTH+x);
					if ( entry->offset > 0 && entry->size )
						mchk->SetIfixObjects( entry->size/sizeof(IFIXENTRY), (PIFIXENTRY)((char*)ifixHeader + entry->offset) );
				}
			}
		}
	}

	//if ( xschIndex ==2 && yschIndex == 4 )
	if (1) // control to load or not
	{
		// load all ireg objects
		char* iregFile = u7Files->iregFiles[iregFileIndex];
		long iregSize = u7Files->iregFileSizes[iregFileIndex];
		if ( iregFile && iregSize > 0 )
			ParseIregFile( xchStart, ychStart, iregSize, iregFile, iregFile );
	}
}

const char* WorldMap::ParseIregFile(
	int xchkStart, int ychkStart, int size, const char* base, const char* cur, IregObject* container )
{
	const char* p = cur;
	int zeroCount = 0;
	while ( p - base < size )
	{
		int offset = p - base;

		if ( *p == 0 )
		{
			if ( container )
			{
				assert(false);
				p++;
				return p;
			}
			else
			{
				zeroCount++;
				p++;
				continue; // next chunk
			}
		}

		if ( *p == 1 )
		{
			assert(container);
			p++;
			return p;
		}

		switch (*p) {
		case 0x06:
			{
				PIREG_STANDARD_ENTRY ent = (PIREG_STANDARD_ENTRY)p;
				p += (*p) + 1;

				BasicObject* newObj = new IregObject( this, ent->shapeID, (const unsigned char*)ent );
				if ( !container )
				{
					MapChunk* mchk = GetMapChunk( xchkStart + ent->xchunk, ychkStart + ent->ychunk );
					mchk->Add( newObj, ent->xtile, ent->ytile, ent->zlevel );
				}
				else
				{
					container->AddObject(newObj);
				}
			}
			break;
		case 0x0C:
			{
				PIREG_EXTENDED_ENTRY ent = (PIREG_EXTENDED_ENTRY)p;
				p += (*p) + 1;

				const TFA* pTFA = GetShapeTFA(u7Files->tfaFile,ent->shapeID.shapeType);
				if ( pTFA->shapeClass == SC_HATCHABLE )
				{
					assert(!container);
					BasicObject* newObj = new IregObject( this, ent->shapeID, (const unsigned char*)ent );
					MapChunk* mchk = GetMapChunk( xchkStart + ent->xchunk, ychkStart + ent->ychunk );
					mchk->Add( newObj, ent->xtile, ent->ytile, ent->general.zlevel );
				}
				else if ( pTFA->shapeClass == SC_BARGE )
				{
					assert(!container);
					BasicObject* newObj = new IregObject( this, ent->shapeID, (const unsigned char*)ent );
					MapChunk* mchk = GetMapChunk( xchkStart + ent->xchunk, ychkStart + ent->ychunk );
					mchk->Add( newObj, ent->xtile, ent->ytile, ent->general.zlevel );
				}
				else if ( pTFA->shapeClass == SC_VIRTUALSTONE )
				{
					assert(!container);
					BasicObject* newObj = new IregObject( this, ent->shapeID, (const unsigned char*)ent );
					MapChunk* mchk = GetMapChunk( xchkStart + ent->xchunk, ychkStart + ent->ychunk );
					mchk->Add( newObj, ent->xtile, ent->ytile, ent->general.zlevel );
				}
				else
				{
/*
797 scroll
798 sealed box
799 unsealed box
800 chest
801 backpack
802 bag
803 basket
804 crate
*/
					assert( pTFA->shapeClass == SC_CONTAINER );
					
					IregObject* newObj = new IregObject( this, ent->shapeID, (const unsigned char*)ent );
					if ( !container )
					{
						MapChunk* mchk = GetMapChunk( xchkStart + ent->xchunk, ychkStart + ent->ychunk );
						mchk->Add( newObj, ent->xtile, ent->ytile, ent->container.zlevel );
					}
					else
					{
						container->AddObject(newObj);
					}

					// (content is present if shapeID represents a container and type not null)
					if ( ent->container.referent != 0 )
					{
						p = ParseIregFile( xchkStart, ychkStart, size, base, (char*)(ent+1), newObj );
					}
				}
			}
			break;
		case 0x12:
			{
				// spellbook
				PIREG_SPELLBOOK_ENTRY ent = (PIREG_SPELLBOOK_ENTRY)p;
				p += (*p) + 1;

				SpellbookObject* newObj = new SpellbookObject( this, ent->shapeID, (const unsigned char*)ent );
				if ( !container )
				{
					MapChunk* mchk = GetMapChunk( xchkStart + ent->xchunk, ychkStart + ent->ychunk );
					mchk->Add( newObj, ent->xtile, ent->ytile, ent->zlevel );
				}
				else
				{
					container->AddObject(newObj);
				}
			}
			break;
		default:
			assert(false);
			p += (*p) + 1;
			break;
		}		
	}
	assert( p - base == size );

	return p;
}

void WorldMap::LoadNPCs( int iregFileIndex )
{
	int xschIndex = (iregFileIndex%SUPERCHUNKS_PER_MAP_IN_WIDTH);
	int yschIndex = (iregFileIndex/SUPERCHUNKS_PER_MAP_IN_WIDTH);

	int xchkStart = xschIndex * CHUNKS_PER_SUPERCHUNK_IN_WIDTH;
	int ychkStart = yschIndex * CHUNKS_PER_SUPERCHUNK_IN_HEIGHT;

	// only add npcs who are in this superchunk
	PNBUF pnbuf = (PNBUF)u7Files->u7nbufFile;
	int count = u7Files->u7nbufFileSize / sizeof(NBUF);
	count = 356; // information from npc.dat inside initgame.dat

	PREADIED pReadied = (PREADIED)(u7Files->u7nbufFile + count * sizeof(NBUF) + 4);

	for ( int i = 0; i < count; i++ )
	{
		if ( pnbuf->referent % sizeof(IBUF) == 0)
		{
			PIBUF pibuf1 = (PIBUF)(u7Files->u7ibufFile + pnbuf->referent);
			PIBUF pibuf2 = (PIBUF)(u7Files->u7ibufFile + pibuf1->standard.ext_ref);

			int hp = pibuf2->extended.data2;

			if ( pibuf2->extended.superchunk == iregFileIndex )
			{
				IregObject* npc = new IregObject( this, pibuf1->standard.shapeID, NULL );
				MapChunk* mchk = GetMapChunk(
					xchkStart + pibuf1->standard.xchunk,
					ychkStart + pibuf1->standard.ychunk );
				mchk->Add( npc,
					pibuf1->standard.xtile,
					pibuf1->standard.ytile,
					pibuf2->extended.zlevel );

				LoadIbufObject( pibuf2->extended.next, pnbuf->referent, npc, pReadied + i );
			}
		}
		else
			i = i; // for debug
		pnbuf++;
	}
}

void WorldMap::LoadIbufObject( int offset, int ownerOffset, IregObject* container, PREADIED pReadied )
{
	while ( offset != 0 )
	{
		PIBUF pibuf1 = (PIBUF)(u7Files->u7ibufFile + offset);
		assert( pibuf1->standard.referent == ownerOffset );

		bool readied = false;

		const TFA* pTFA = GetShapeTFA(u7Files->tfaFile,pibuf1->standard.shapeID.shapeType);
		if ( pTFA->shapeClass == SC_CONTAINER )
		{
			PIBUF pibuf2 = (PIBUF)(u7Files->u7ibufFile + pibuf1->standard.ext_ref);

			readied = (pibuf2->extended.info & 0x01) == 0x01;

			IregObject* newObj = new IregObject( this, pibuf1->standard.shapeID, NULL );
			container->AddObject(newObj);
			LoadIbufObject( pibuf2->extended.next, offset, newObj, pReadied );
		}
		else
		{
			int quantity = 1;
			switch ( pTFA->shapeClass ) {
			case SC_QUANTITY:
				{
					quantity = pibuf1->standard.flag & 0x7F;
					bool ok_to_take = (pibuf1->standard.flag & 0x80) == 0x80;
				}
				break;
			case SC_QUALITY_FLAGS:
				{
					int quality = pibuf1->standard.flag;
					bool invisible = (quality&0x01) == 0x01;
					bool ok_to_take = (quality&0x08) == 0x08;
				}
				break;
			}

			readied = (pibuf1->standard.info & 0x01) == 0x01;

			BasicObject* newObj = new BasicObject( this, pibuf1->standard.shapeID );
			container->AddObject(newObj);
		}

		if ( readied )
		{
			for ( int pos = 0; pos < 12; pos++ )
			{
				if ( pReadied->position[pos] == offset )
					break;
			}
		}

		offset = pibuf1->standard.next;
	}
}

void WorldMap::ReadData( int viewportX, int viewportY, int viewportWidth, int viewportHeight )
{
	//
	// base on the viewport to prepare map chunks
	//
	assert( 0 <= viewportX && viewportX < PIXELS_PER_MAP_IN_WIDTH &&
		0 <= viewportY && viewportY < PIXELS_PER_MAP_IN_HEIGHT );

	// need to check how the renderer paint chunks. Renderer::Paint(...)
	int xschStart = viewportX / PIXELS_PER_SUPERCHUNK_IN_WIDTH;
	int yschStart = viewportY / PIXELS_PER_SUPERCHUNK_IN_HEIGHT;
	int xschEnd = (viewportX + viewportWidth + PIXELS_PER_SUPERCHUNK_IN_WIDTH) / PIXELS_PER_SUPERCHUNK_IN_WIDTH;
	int yschEnd = (viewportY + viewportHeight + PIXELS_PER_SUPERCHUNK_IN_HEIGHT + PIXELS_PER_CHUNK_IN_HEIGHT) / PIXELS_PER_SUPERCHUNK_IN_HEIGHT;

	for ( int y = yschStart; y < yschEnd; y++ )
	{
		for ( int x = xschStart; x < xschEnd; x++ )
		{
			int xschIndex = (x%SUPERCHUNKS_PER_MAP_IN_WIDTH);
			int yschIndex = (y%SUPERCHUNKS_PER_MAP_IN_HEIGHT);
			int schunkIndex = xschIndex + yschIndex * SUPERCHUNKS_PER_MAP_IN_WIDTH;
			if ( !loadedSuperchunks[schunkIndex] )
			{
				loadedSuperchunks[schunkIndex] = true;
				LoadSuperchunk( schunkIndex );
				LoadNPCs( schunkIndex );
			}
		}
	}
}

void WorldMap::HitTest( int worldX, int worldY, int zlimit, OBJLIST& hits ) const
{
	assert( worldX >= 0 && worldY >= 0
		&& worldX < PIXELS_PER_MAP_IN_WIDTH && worldY < PIXELS_PER_MAP_IN_HEIGHT );

	int xchk = worldX / PIXELS_PER_CHUNK_IN_WIDTH;
	int ychk = worldY / PIXELS_PER_CHUNK_IN_HEIGHT;

	MapChunk* checked;
	if ( (checked = mapChunks[xchk][ychk]) != NULL )
		checked->HitTest(worldX,worldY,zlimit,hits);

	int right = INCR_CHUNK(xchk);
	int below = INCR_CHUNK(ychk);

	if ( (checked = mapChunks[right][ychk]) != NULL )
		checked->HitTest(worldX,worldY,zlimit,hits);

	if ( (checked = mapChunks[xchk][below]) != NULL )
		checked->HitTest(worldX,worldY,zlimit,hits);

	if ( (checked = mapChunks[right][below]) != NULL )
		checked->HitTest(worldX,worldY,zlimit,hits);
}
